# ily - an obfuscator for Python scripts

## Usage
```./obfuscator.py <obfuscations> <filename>```

`<obfuscations>` - string, containing a sequence of obfuscations to apply left-to-right.  
`<filename>` - filename of the program to obfuscate.

## Output
ily will output the obfuscated result to STDOUT.

## Available obfuscations

### IntInflationObfuscation
Obfuscation character: `i`  
Obfuscation safety: **Safe**

This obfuscation replaces every integer constant with a sum of random integers:
```python
def mult_by_5(x):
    return 5 * x

print(mult_by_5(10))
```
```python
def mult_by_5(x):
    return (--16 + -171 + -313 + -173 + --15 + -685 + --1316) * x
print(mult_by_5(-993 + -306 + --77 + --794 + -105 + -667 + --29 + -911 + --67 + --2025))
```

### StringInflationObfuscation
Obfuscation character: `s`  
Obfuscation safety: **Safe**  

This obfuscation replaces every string constant with a sum of `chr()` calls:

```python
print("Hello, world!")
```
```python
print(chr(72) + chr(101) + chr(108) + chr(108) + chr(111) + chr(44) + chr(32) + chr(119) + chr(111) + chr(114) + chr(108) + chr(100) + chr(33))
```

### HideImportsObfuscation
Obfuscation character: `m`  
Obfuscation safety: **Safe**

Wraps imports inside an `exec()` call in order to be further obfuscated by some other obfuscation (like StringInflationObfuscation)

```python
import random

print(random.randint(10, 20))
```

```python
exec('import random')
print(random.randint(10, 20))
```

### VariableNameObfuscation
Obfuscation character: `v`  
Obfuscation safety: **Relatively safe**

This obfuscation replaces all top-level names (including `import`'s and `from a import b`'s, in the second case `b` will be obfuscated) with gibberish.

**NOTE:** This obfuscation does not obfuscate class methods and attributes (Nested classes are not obfuscated at all), since it is impossible to determine the type of an expression without running the program, thus making it impossible to figure out whether the attriubute name should be obfuscated or not.

```python
class SomeClass:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def get_a(self):
        return self.a

    def get_b(self):
        return self.b

def some_func():
    alpha = 13
    beta = 37
    
    return str(alpha) + str(beta)

print(SomeClass(2, 3).get_a())
print(SomeClass(5, 6).get_b())
print(some_func())
```
```python
class WIQXJMIUJSUJKNFKN:

    def __init__(PWRXDSUBTGVWFO, DJMCCKOVVCJ, UWTDMFEB):
        PWRXDSUBTGVWFO.a = DJMCCKOVVCJ
        PWRXDSUBTGVWFO.b = UWTDMFEB

    def get_a(PWRXDSUBTGVWFO):
        return PWRXDSUBTGVWFO.a

    def get_b(PWRXDSUBTGVWFO):
        return PWRXDSUBTGVWFO.b

def MICWCKPPQMNYYHUARS():
    SPMTLGTUGXLOAVRQIJKK = 13
    PHQOLPIOECPKWOYYQPIL = 37
    return str(SPMTLGTUGXLOAVRQIJKK) + str(PHQOLPIOECPKWOYYQPIL)
print(WIQXJMIUJSUJKNFKN(2, 3).get_a())
print(WIQXJMIUJSUJKNFKN(5, 6).get_b())
print(MICWCKPPQMNYYHUARS())
```

### AttributeAccessObfuscation
Obfuscation character: `a`  
Obfuscation safety: **Relatively unsafe** (Not tested much)

This obfuscation replaces attribute accesses with `getattr()` and `setattr()` where applicable:

```python
import random
import string

class RandomStringBuilder:
    def __init__(self, length):
        self.length = length
    
    def build_random_string(self):
        return "".join([random.choice(string.ascii_letters) for i in range(self.length)])
        
print(RandomStringBuilder(13).build_random_string())
```
```python
import random
import string

class RandomStringBuilder:

    def __init__(self, length):
        setattr(self, 'length', length)

    def build_random_string(self):
        return getattr('', 'join')([getattr(random, 'choice')(getattr(string, 'ascii_letters')) for i in range(getattr(self, 'length'))])
print(getattr(RandomStringBuilder(13), 'build_random_string')())
```

### BuiltinsObfuscation
Obfuscation character: `b`  
Obfuscation safety: **Relatively unsafe** (Not tested much)

This obfuscation wraps all built-ins in `builtins.`, for example: `range` turns into `builtins.range`. It also wraps `True`, `False` and `None` into `builtins.getattr(builtins, 'True')`, `builtins.getattr(builtins, 'False')` and `builtins.getattr(builtins, 'None')` respectively.

**NOTE:** This obfuscation breaks if user code has redefined built-in names. For example, if the program has a `def input()` or a `def hash(x)`, this obfuscation won't work properly.

[Source](https://rosettacode.org/wiki/Negative_base_numbers#Python)
```python
def EncodeNegBase(n, b): #Converts from decimal
	if n == 0:
		return "0"
	out = []
	while n != 0:
		n, rem = divmod(n, b)
		if rem < 0:
			n += 1
			rem -= b
		out.append(rem)
	return "".join(map(str, out[::-1]))

def DecodeNegBase(nstr, b): #Converts to decimal
	if nstr == "0":
		return 0
	
	total = 0
	for i, ch in enumerate(nstr[::-1]):
		total += int(ch) * b**i
	return total

if __name__=="__main__":
	
	print ("Encode 10 as negabinary (expect 11110)")
	result = EncodeNegBase(10, -2)
	print (result)
	if DecodeNegBase(result, -2) == 10: print ("Converted back to decimal")
	else: print ("Error converting back to decimal")

	print ("Encode 146 as negaternary (expect 21102)")
	result = EncodeNegBase(146, -3)
	print (result)
	if DecodeNegBase(result, -3) == 146: print ("Converted back to decimal")
	else: print ("Error converting back to decimal")

	print ("Encode 15 as negadecimal (expect 195)")
	result = EncodeNegBase(15, -10)
	print (result)
	if DecodeNegBase(result, -10) == 15: print ("Converted back to decimal")
	else: print ("Error converting back to decimal")
```
```python
import builtins

def EncodeNegBase(n, b):
    if n == 0:
        return '0'
    out = []
    while n != 0:
        n, rem = builtins.divmod(n, b)
        if rem < 0:
            n += 1
            rem -= b
        out.append(rem)
    return ''.join(builtins.map(builtins.str, out[::-1]))

def DecodeNegBase(nstr, b):
    if nstr == '0':
        return 0
    total = 0
    for i, ch in builtins.enumerate(nstr[::-1]):
        total += builtins.int(ch) * b ** i
    return total
if __name__ == '__main__':
    builtins.print('Encode 10 as negabinary (expect 11110)')
    result = EncodeNegBase(10, -2)
    builtins.print(result)
    if DecodeNegBase(result, -2) == 10:
        builtins.print('Converted back to decimal')
    else:
        builtins.print('Error converting back to decimal')
    builtins.print('Encode 146 as negaternary (expect 21102)')
    result = EncodeNegBase(146, -3)
    builtins.print(result)
    if DecodeNegBase(result, -3) == 146:
        builtins.print('Converted back to decimal')
    else:
        builtins.print('Error converting back to decimal')
    builtins.print('Encode 15 as negadecimal (expect 195)')
    result = EncodeNegBase(15, -10)
    builtins.print(result)
    if DecodeNegBase(result, -10) == 15:
        builtins.print('Converted back to decimal')
    else:
        builtins.print('Error converting back to decimal')
```

## FAQ

Q: Why even make a Python obfuscator, if readabilty is Python's big thing?  
A: I was bored.

Q: Is this safe to use commercially?  
A: If you are brave enough, but most likely no. I don't recommend this.

Q: Feature \<insert feature name here\> is not supported. Will you update *ily* to support it?  
A: Probably not, unless it is **extensively** used.

Q: What does *ily* stand for?  
A: It stands for *I love you*, thank you for asking :)
